package com.dbflow5.contentobserver;

import com.dbflow5.*;
import com.dbflow5.config.FlowManager;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.OhosSQLiteOpenHelper;
import com.dbflow5.query.OperatorGroup;
import com.dbflow5.query.SQLOperator;
import com.dbflow5.query.SQLite;
import com.dbflow5.runtime.ContentResolverNotifier;
import com.dbflow5.runtime.FlowContentObserver;
import com.dbflow5.structure.ChangeAction;
import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry;
import ohos.utils.net.Uri;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.function.BiFunction;

import static org.junit.Assert.assertEquals;

public class ContentObserverTest {
    @Rule
    public DBFlowInstrumentedTestRule dblflowTestRule = DBFlowInstrumentedTestRule.create(builder -> {
        builder.database(ContentObserverDatabase.class, builder1 -> {
            builder1.modelNotifier(new ContentResolverNotifier(AbilityDelegatorRegistry.getAbilityDelegator().getAppContext(), "com.grosner.content"));
            builder1.transactionManagerCreator(ImmediateTransactionManager::new);
            return null;
        }, OhosSQLiteOpenHelper.createHelperCreator(AbilityDelegatorRegistry.getAbilityDelegator().getAppContext()));
        return null;
    });

    public String contentUri = "com.grosner.content";

    private User user;

    @Before
    public void setupUser() {
        FlowManager.database(ContentObserverDatabase.class, dbFlowDatabase -> {
            SQLite.delete(User.class).execute(dbFlowDatabase);
            return null;
        });
        user = new User(5, "Something", 55);
    }

    @Test
    public void testSpecificUris() {
        OperatorGroup conditionGroup = FlowManager.modelAdapter(User.class).getPrimaryConditionClause(user);
        Uri uri = SqlUtils.getNotificationUri(contentUri,
                User.class, ChangeAction.DELETE,
                conditionGroup.conditions());

        assertEquals(uri.getDecodedAuthority(), contentUri);
        assertEquals(FlowManager.tableName(User.class), uri.getFirstQueryParamByKey(SqlUtils.TABLE_QUERY_PARAM));
        assertEquals(uri.getDecodedFragment(), ChangeAction.DELETE.name());
        assertEquals(Uri.decode(uri.getFirstQueryParamByKey(Uri.encode(User_Table.id.getQuery()))), "5");
        assertEquals(Uri.decode(uri.getFirstQueryParamByKey(Uri.encode(User_Table.name.getQuery()))), "Something");
    }

    @Test
    public void testSpecificUrlInsert() throws InterruptedException {
//        assertProperConditions(ChangeAction.INSERT, (user, db) -> {
//            Model.insert(User.class, user, db);
//            return null;
//        });
    }

    @Test
    public void testSpecificUrlUpdate() {
        // assertProperConditions(ChangeAction.UPDATE) { user, db -> user.apply { age = 56 }.update(db) }

    }

    @Test
    public void testSpecificUrlSave() {
        // insert on SAVE
        //assertProperConditions(ChangeAction.INSERT) { user, db -> user.apply { age = 57 }.save(db) }
    }

    @Test
    public void testSpecificUrlDelete() {
        // user.save(databaseForTable<User>())
        // assertProperConditions(ChangeAction.DELETE) { user, db -> user.delete(db) }
    }

    private void assertProperConditions(ChangeAction action, BiFunction<User, DatabaseWrapper, Void> userFunc) throws InterruptedException {
        FlowContentObserver contentObserver = new FlowContentObserver(contentUri, null);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        MockOnModelStateChangedListener mockOnModelStateChangedListener = new MockOnModelStateChangedListener(countDownLatch);
        contentObserver.addModelChangeListener(mockOnModelStateChangedListener);
        contentObserver.registerForContentChanges(DemoApp.context, User.class);

        userFunc.apply(user, FlowManager.databaseForTable(User.class, dbFlowDatabase -> null));
        countDownLatch.await();

        SQLOperator[] ops = mockOnModelStateChangedListener.operators;
        assertEquals(2, ops.length);
        assertEquals(ops[0].columnName(), User_Table.id.getQuery());
        assertEquals(ops[1].columnName(), User_Table.name.getQuery());
        assertEquals(ops[1].value(), "Something");
        assertEquals(ops[0].value(), "5");
        assertEquals(action, mockOnModelStateChangedListener.action);

        contentObserver.removeModelChangeListener(mockOnModelStateChangedListener);
        contentObserver.unregisterForContentChanges(DemoApp.context);
    }

    static class MockOnModelStateChangedListener implements FlowContentObserver.OnModelStateChangedListener {
        public CountDownLatch countDownLatch;
        public ChangeAction action = null;
        public SQLOperator[] operators = null;

        public MockOnModelStateChangedListener(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void onModelStateChanged(Class<?> table, ChangeAction action, SQLOperator[] primaryKeyValues) {
            this.action = action;
            operators = primaryKeyValues;
            countDownLatch.countDown();
        }
    }
}
